/**
 *  package: com.softtechdesign.ga
 *  class: GARoute.java
 */
package com.softtechdesign.ga;


import java.util.Random;

import com.TabuSearch.MyTSsolution;
import com.mdvrp.Cost;
import com.mdvrp.Customer;
import com.mdvrp.Instance;
import com.mdvrp.MyLogger;
import com.mdvrp.Parameters;



/**
 * @author Angelo Prudentino
 * @date 22/nov/2014
 */
public class GARoute extends GA{
	
	private static String class_name = GARoute.class.getName();
	private static MyLogger MyLog = new MyLogger(class_name);

	private static final double LOAD_RATIO = 0.10; //used in initPopualtion
	private double alpha;
	private double beta;
	private double gamma;
	private double delta;
	
	/*
	 * greedyRatio represents the fraction of the population which has to be generated by 
	 * means of the greedy randomized approach of the thesis.   
	 */
	private double greedyRatio;
	     
    /**
     * @param parameters
     * @param instance
     */
    public GARoute(Parameters p, Instance instance) {
	
	            super(p.getChromosomeDim(),
			          p.getPopulationDim(), 
					  p.getCrossoverProb(), 
					  p.getRandomSelectionChance(),
					  p.getMaxGenerations(), 
					  p.getMutationProb(), 
					  p.getCrossoverType(), 
					  p.isComputeStatistics(),
					  p.getGreedyRatio(),
					  instance);
		
		super.setInstance(instance);
		
		super.setInstance(instance); 
		this.best_feasible_sol = new MyGAsolution(instance);
		this.greedyRatio = p.getGreedyRatio();
		
		//create the chromosomes for this population
        for (int i = 0; i < populationDim; i++)
        {
            this.chromosomes[i] = new ChromCustomer(chromosomeDim, instance);
            this.chromNextGen[i] = new ChromCustomer(chromosomeDim, instance);
            this.prelimChrom[i] = new ChromCustomer(chromosomeDim, instance);
        }

        initPopulation();
        ChromCustomer chr = (ChromCustomer) this.chromosomes[this.bestFitnessChromIndex];
        best_feasible_sol.UpdateSolution(chr);
        MyLog.info(class_name, "constructor", "Best Initial Chromosome: ");
        MyLog.info(class_name, "constructor", chr.getGenesAsStr() + " Fitness= " + chr.fitness + "\n--------------------------------------------------");

    }

    /**
	 * @author Daniele Faveria
	 * @date 22/nov/2014
     */
    @Override
    protected void initPopulation() {
    	int v;
    	int c = 0;
    	int NUM_VEHIC = instance.getVehiclesNr();
    	int NUM_CUST = instance.getCustomersNr();
    	Random random = instance.getRandom();
    	
    	boolean[] assignedCust = new boolean[NUM_CUST]; 
    	//number of not assigned customers:
    	int notAssigned;
    	int nGreedy = (int) (greedyRatio * populationDim); //decimal figures are lost (round down)
    	
    	//for each chromosome to be generated through greedy randomized approach
    	for(int chrom = 0; chrom < nGreedy; chrom++){
    		instance.getParameters().setStartClient(-1);	//we want the startClient to be picked randomly each time
    		MyTSsolution tsSol = new MyTSsolution(instance, true); //create the greedy solution
    		MyGAsolution gaSol = tsSol.ConvertTSGA();
    		ChromCustomer chromo = gaSol.getSolution();
    		this.chromosomes[chrom].copyChromGenes(chromo);
        	this.chromosomes[chrom].fitness = getFitness(chrom);
    	}
    	
    	//for each chromosome to be generated randomly 
        for (int chrom = nGreedy; chrom < populationDim; chrom++){
        	ProtoChromosome temp = new ProtoChromosome(instance);
        	notAssigned = NUM_CUST;
        	for(int i=0; i<NUM_CUST; i++)
        		assignedCust[i] = false;
        	
        	//until all customers are assigned:
        	while(notAssigned > 0){
        		//find a cust which is still not served
        		if ((double)(notAssigned)/NUM_CUST > LOAD_RATIO) {
        			//choose randomly
	        		do {
		            	c = random.nextInt(NUM_CUST);
	        		} while (assignedCust[c] == true);
        		}
        		else {
        			//scan the vector
        			do {
        				c = (++c) % NUM_CUST;
        			} while (assignedCust[c] == true);
        		}
            		
        		//chose a random vehicle to start
            	v = random.nextInt(NUM_VEHIC);
        		
        		//cycle through each vehicle
        		for (int i = 0; i<NUM_VEHIC; i++) {
        			Customer chosenCust = instance.getCustomerByNumID(c);
        			
        			//verify the Capacity & Duration.
        			if(temp.checkCapacity(v, chosenCust) && temp.checkDuration(v, chosenCust)){
	        			//if it's ok to be added, assign it (add to temp) 
	        			temp.addGene(v, chosenCust);
        				//disable customer (already served) setting the assignedCust
        				assignedCust[c] = true;
        				notAssigned--;
        				break;
	        		}
        			//if it's not ok to be added
        			//cycle through the others vehicle v
        			v=(v+1)%NUM_VEHIC;
        		}
        		//if customer is not assignable, then the protosolution must be rebuilt from scratch.
        		if (assignedCust[c] == false){
        			//repeat last iteration of chrom for.
        			//System.out.println("Unfeasible, redoing");
        			chrom--;
        			break;
        		}
        		//if customer was assigned we can go on with the next customer
        	}
        	
        	//if it's a feasible solution then build the chromosome from ProtoChromosome
        	if (notAssigned == 0){
        		Chromosome chromo = temp.toChromosome();
        		this.chromosomes[chrom].copyChromGenes(chromo);
            	this.chromosomes[chrom].fitness = getFitness(chrom);
        		//System.out.println("Finished chromosome " + chrom);
        	}
        	
    }
    //System.out.println("Finished with the initial generation");
}

    /** 
     * @see com.softtechdesign.ga.GA#doRandomMutation(int)
     */
    @Override
    protected void doRandomMutation(int iChromIndex) {
    }

    /** 
     * @see com.softtechdesign.ga.GA#doOnePtCrossover(com.softtechdesign.ga.Chromosome, com.softtechdesign.ga.Chromosome)
     */
    @Override
    protected void doOnePtCrossover(Chromosome Chrom1, Chromosome Chrom2) {
    	//not used
    }

    /** 
     * @see partially mapped crossover pmX
     * @author: Luca Boni
     * @date: 28/11/2014
     */
    @Override
    protected void doTwoPtCrossover(Chromosome Chrom1, Chromosome Chrom2) {
    	ChromCustomer parent1 = (ChromCustomer)Chrom1;
    	ChromCustomer parent2 = (ChromCustomer)Chrom2;
    	ChromCustomer child1 = new ChromCustomer(parent1.length(), instance);
    	ChromCustomer child2 = new ChromCustomer(parent2.length(), instance);
		
		int depotNumberIdentifier = parent1.getGene(0).getNumber();
		int cutPoint1 = (int)(Math.random()*chromosomeDim);
		int cutPoint2;// = (int)(Math.random()*chromosomeDim);
		
		while((cutPoint2 = (int)(Math.random()*chromosomeDim)) == cutPoint1); // to avoid cutPoint1 == cutPoint2
		
		//cut Point1 must be the first one
		if(cutPoint1 > cutPoint2){
			int tmp=cutPoint1;
			cutPoint1=cutPoint2;
			cutPoint2=tmp;
		}
		
    	    try{
    	    	//to translate repetition of depot value into unused values
    	    	//pmX need chromosomes without repetitions
    	    	int encriptedDepotParent1 = encriptDepotCode(parent1,depotNumberIdentifier);
    			int encriptedDepotParent2 = encriptDepotCode(parent2,depotNumberIdentifier);
    			//the +1 represents the "original" depot, the first, which isn't changed.
    			//int transaltedPopulationDim = chromosomeDim + encriptedDepotParent1 + 1; 
    			
//    			boolean[] usedValuesChild1 = new boolean[transaltedPopulationDim];
//    			boolean[] usedValuesChild2 = new boolean[transaltedPopulationDim];
    			boolean[] usedValuesChild1 = new boolean[chromosomeDim];
    			boolean[] usedValuesChild2 = new boolean[chromosomeDim];
    	    	
    			//initialization of boolean arrays
    			for(int i=0; i<chromosomeDim; i++){
    				usedValuesChild1[i] = false;
    				usedValuesChild2[i] = false;
    				}
    			
    			//part between cut points
    			for(int i = cutPoint1; i < cutPoint2; i++){
    				child1.setGene(parent2.getGene(i), i);
    				int n1 = child1.getGene(i).getNumber();
    				usedValuesChild1[n1] = true;
    				child2.setGene(parent1.getGene(i), i);
    				int n2 = child2.getGene(i).getNumber();
    				usedValuesChild2[n2] = true;
    			}
    			
    			//part after second cut point
    			for(int i = cutPoint2; i < chromosomeDim; i++){
    				
    				//about child 1
    				int pos = applyPmxRule(i, usedValuesChild1, parent1, parent2);
    				child1.setGene(parent1.getGene(pos), i);
    				int n1 = child1.getGene(i).getNumber();
    				usedValuesChild1[n1] = true;
    				
    				//about child 2
    				pos = applyPmxRule(i, usedValuesChild2, parent2, parent1);
    				child2.setGene(parent2.getGene(pos), i);
    				int n2 = child2.getGene(i).getNumber();
    				usedValuesChild2[n2] = true;
    			}
    			
    			//part before first cut point
    			for(int i=0; i<cutPoint1; i++){
    				
    				//about child 1
    				int pos = applyPmxRule(i, usedValuesChild1, parent1, parent2);
    				child1.setGene(parent1.getGene(pos), i);
    				usedValuesChild1[parent1.getGene(pos).getNumber()] = true;
    				
    				//about child 2
    				pos = applyPmxRule(i, usedValuesChild2, parent2, parent1);
    				child2.setGene(parent2.getGene(pos), i);
    				usedValuesChild2[parent2.getGene(pos).getNumber()] = true;
    			}
    			
    			int decriptedDepotChild1 = decriptDepotCode(child1, depotNumberIdentifier);
				if(decriptedDepotChild1 != encriptedDepotParent1) throw new GAException("translation error 1");
				
				int decriptedDepotChild2 = decriptDepotCode(child2, depotNumberIdentifier);
				if(decriptedDepotChild2 != encriptedDepotParent2) throw new GAException("translation error 2");
    			
    		}catch(GAException e){
//    			MyLog.err(class_name, "doTwoPtCrossover(Chromosome Chrom1, Chromosome Chrom2)", e.getMessage());
    			e.printStackTrace();
    		}
    	
	    	parent1.copyChromGenes(child1);
	    	parent2.copyChromGenes(child2);
    }

    private int encriptDepotCode(ChromCustomer parentChromosome, int depotNumber) {
		int count=0;
		boolean first=true;
		for(int i=0; i<parentChromosome.length(); i++){
			if(parentChromosome.getGene(i).getNumber() == depotNumber){
				if(first){first=false;}
				else{
					count++;
					//parentChromosome.getGene(i).setNumber(popDimension + count);
					//i create a jolly customer, not initialized, just with the right Number.
					Customer jolly = new Customer();
					jolly.setNumber(depotNumber + count);
					parentChromosome.setGene(jolly, i);
				}
			}
		}
		return count;
	}    
	
	private int decriptDepotCode(ChromCustomer childChromosome, int depotNumber) {
		int count=0;
		int iter = childChromosome.length();
		for(int i=0; i<iter; i++){
			if(childChromosome.getGene(i).getNumber() > depotNumber){
				//childChromosome.getGene(i).setNumber(depotNumber);
				childChromosome.setGene(childChromosome.getGene(0), i);
				count++;
			}
		}
		return count;
	}
    
	private int applyPmxRule (int i, boolean[] booleanArray, ChromCustomer parentA, ChromCustomer parentB) throws GAException{
		int pos = i;
		int number = parentA.getGene(pos).getNumber();
		while(booleanArray[number] == true){
			pos = parentB.getPositionNumber(parentA.getGene(pos));
			number = parentA.getGene(pos).getNumber();
		}
		if(pos < 0) 
			throw new GAException("Crossover error"); 
		return pos;
	}
    
        /** 
         * @see com.softtechdesign.ga.GA#doUniformCrossover(com.softtechdesign.ga.Chromosome, com.softtechdesign.ga.Chromosome)
         */
        @Override
        protected void doUniformCrossover(Chromosome Chrom1, Chromosome Chrom2) {
        	//not used
        }
    
        /** 
         * @see com.softtechdesign.ga.GA#getFitness(int)
         */
        @Override
        protected double getFitness(int iChromIndex) {
    	
    	ChromCustomer chrom = (ChromCustomer) chromosomes[iChromIndex];
    	
    	Cost c = chrom.getCost();
    	// i consider all kind of violations in the same way
    	updateParamaters();
    	double gen_viol = c.getDepotTwViol()/alpha + c.getDurationViol()/beta + c.getLoadViol()/gamma + c.getTwViol()/delta; 
    	double travel = c.getTravel();
    	return 1/(travel + gen_viol);
        }

		/**
		 * @return the best_sol
		 */
		public MyGAsolution getBestSol() {
			best_feasible_sol.UpdateSolution((ChromCustomer) chromosomes[bestFitnessChromIndex]);
			return best_feasible_sol;
		}
		
		private void updateParamaters(){
			
			Random r = instance.getRandom();
			
			alpha = r.nextDouble()*10;
			beta = r.nextDouble()*10;
			gamma = r.nextDouble()*10;
			delta = r.nextDouble()*10;
		}
		
}
